package database

import (
	"context"        // 提供上下文功能，用于控制数据库操作的生命周期（如超时、取消）
	"database/sql"   // 标准库的数据库接口，提供通用的SQL数据库操作能力
	"fmt"            // 提供字符串格式化功能，用于构建数据库连接字符串
	"gameServer/log" // 日志包，用于记录数据库操作相关日志
	"time"           // 提供时间相关功能，用于设置连接超时等时间参数

	// 必须导入MySQL驱动程序 空白导入
	_ "github.com/go-sql-driver/mysql"
)

/*
本文件实现了MySQL数据库的具体操作，基于基础数据库接口（Database）扩展，
封装了MySQL的连接、断开、查询、事务等核心功能，使上层代码可通过统一接口操作MySQL。
*/

// MySqlDatabase MySQL数据库的具体实现结构体，
// 嵌入BaseDatabase继承配置管理功能，同时持有底层sql.DB实例用于实际数据库操作
type MySqlDatabase struct {
	BaseDatabase         // 嵌入基础数据库结构体，继承配置获取等通用功能
	db           *sql.DB // 底层MySQL连接实例，由标准库sql包提供，封装了MySQL连接和操作
}

// NewMySQLDatabase 创建一个MySQL数据库实例，初始化基础配置
// 参数：
//
//	config: 数据库连接配置指针（包含主机、端口、用户名等信息）
//
// 返回值：
//
//	*MySqlDatabase: 初始化后的MySQL数据库实例
func NewMySQLDatabase(config *Config) *MySqlDatabase {
	return &MySqlDatabase{
		BaseDatabase: *NewBaseDatabase(config), // 初始化基础数据库部分，传入配置
	}
}

//func (db *MySqlDatabase) GetConfig() *Config {
//	return db.config
//}

// Connect 建立与MySQL数据库的连接，并初始化连接池参数
// 步骤：构建连接字符串（DSN）→ 打开数据库连接 → 配置连接池 → 测试连接有效性
// 返回值：
//
//	error: 连接成功返回nil，失败返回具体错误信息（如连接字符串错误、网络问题等）
func (m *MySqlDatabase) Connect() error {
	// 构建MySQL连接字符串（DSN），格式为"用户名:密码@tcp(主机:端口)/数据库名?参数"
	// 参数说明：
	//   m.config.Username: 连接数据库的用户名
	//   m.config.Password: 连接数据库的密码
	//   m.config.Host: 数据库主机IP或域名
	//   m.config.Port: 数据库端口（注意：此处原代码可能有问题，Port为string类型，%d格式化需先转为int）
	//   m.config.Database: 要连接的数据库名称
	//   附加参数：charset=utf8mb4（支持emoji等特殊字符）、parseTime=True（解析时间类型）、loc=Local（使用本地时区）
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
		m.config.Username,
		m.config.Password,
		m.config.Host,
		m.config.Port, // 注意：此处可能需要修正，Port为string，需先转换为int（如使用strconv.Atoi）
		m.config.Database,
	)

	// 打开MySQL连接，"mysql"为驱动名（需提前导入对应驱动，如github.com/go-sql-driver/mysql）
	// 返回的db是连接池，而非单连接
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return fmt.Errorf("failed to open MySQL connection: %v", err) // 包装错误信息，便于排查
	}

	// 配置连接池参数，优化连接复用和资源占用
	db.SetMaxOpenConns(m.config.PoolSize)                                // 设置最大打开连接数（同时使用的连接上限），对应配置的PoolSize
	db.SetMaxIdleConns(m.config.PoolSize / 2)                            // 设置最大空闲连接数（未使用但保持打开的连接上限），通常为最大连接数的一半
	db.SetConnMaxLifetime(time.Duration(m.config.Timeout) * time.Second) // 连接的最大生存时间，超时后会被关闭重建

	// 测试连接有效性，确保连接成功建立
	if err := db.Ping(); err != nil {
		return fmt.Errorf("failed to ping MySQL connection: %v", err) // 连接失败时返回错误
	}

	m.db = db // 保存连接池实例，供后续操作使用
	// 记录连接成功日志，包含连接的主机、端口和数据库名
	log.Infof("MySQL connected to %s:%d/%s", m.config.Host, m.config.Port, m.config.Database)
	return nil // 连接成功
}

// Disconnect 断开与MySQL数据库的连接，释放连接池资源
// 返回值：
//
//	error: 断开成功返回nil，失败返回错误信息（如连接已关闭）
func (m *MySqlDatabase) Disconnect() error {
	if m.db != nil { // 检查连接实例是否存在
		return m.db.Close() // 关闭连接池，释放所有连接资源
	}
	return nil // 连接实例不存在时，视为已断开，返回nil
}

// Ping 检查与MySQL数据库的连接是否有效
// 返回值：
//
//	error: 连接有效返回nil；连接未建立（m.db为nil）返回ErrDBNotConnected；连接无效返回具体错误
func (m *MySqlDatabase) Ping() error {
	if m.db == nil { // 连接未初始化时，返回"未连接"错误
		return ErrDBNotConnected
	}
	return m.db.Ping() // 调用底层连接的Ping方法，检查连接有效性
}

// Type 返回当前数据库的类型，实现Database接口
// 返回值：
//
//	DBType: 固定返回DBTypeMySQL，表示当前是MySQL数据库
func (m *MySqlDatabase) Type() DBType {
	return DBTypeMySQL
}

// GetDB 获取底层的sql.DB实例，允许上层代码进行更复杂的MySQL操作（如自定义事务、高级查询）
// 返回值：
//
//	*sql.DB: 底层MySQL连接池实例；若未连接，返回nil
func (m *MySqlDatabase) GetDB() *sql.DB {
	return m.db
}

// Query 执行SQL查询，返回多行结果（用于SELECT等返回数据集的操作）
// 参数：
//
//	query: SQL查询语句（如"SELECT * FROM users WHERE id = ?"）
//	args: 可变参数，用于替换查询语句中的占位符（?），防止SQL注入
//
// 返回值：
//
//	*sql.Rows: 查询结果集，需调用者手动关闭
//	error: 执行失败返回错误（如未连接、SQL语法错误）；成功返回nil
func (m *MySqlDatabase) Query(query string, args ...interface{}) (*sql.Rows, error) {
	if m.db == nil { // 检查连接是否已建立
		return nil, ErrDBNotConnected
	}
	// 调用底层sql.DB的Query方法执行查询
	return m.db.Query(query, args...)
}

// QueryRow 执行SQL查询，返回单行结果（用于只需要一行数据的查询，如SELECT ... LIMIT 1）
// 参数：
//
//	query: SQL查询语句
//	args: 用于替换占位符的参数
//
// 返回值：
//
//	*sql.Row: 单行结果；若未连接，返回nil
func (m *MySqlDatabase) QueryRow(query string, args ...interface{}) *sql.Row {
	if m.db == nil { // 连接未建立时，返回nil
		return nil
	}
	// 调用底层sql.DB的QueryRow方法，返回单行结果
	return m.db.QueryRow(query, args...)
}

// Exec 执行SQL语句（用于INSERT、UPDATE、DELETE等不返回数据集的操作）
// 参数：
//
//	query: SQL执行语句
//	args: 用于替换占位符的参数
//
// 返回值：
//
//	sql.Result: 包含执行结果（如受影响的行数、自增ID）
//	error: 执行失败返回错误；成功返回nil
func (m *MySqlDatabase) Exec(query string, args ...interface{}) (sql.Result, error) {
	if m.db == nil { // 检查连接状态
		return nil, ErrDBNotConnected
	}
	// 调用底层sql.DB的Exec方法执行语句
	return m.db.Exec(query, args...)
}

// BeginTx 开启一个数据库事务，用于执行一系列需要原子性的操作（要么全部成功，要么全部失败）
// 返回值：
//
//	*sql.Tx: 事务实例，可通过其执行事务内的SQL操作
//	error: 开启事务失败返回错误（如未连接、数据库不支持事务）
func (m *MySqlDatabase) BeginTx() (*sql.Tx, error) {
	if m.db == nil { // 检查连接状态
		return nil, ErrDBNotConnected
	}
	// 调用底层sql.DB的Begin方法开启事务（默认使用空上下文）
	return m.db.Begin()
}

// WithContext 在指定上下文中执行数据库操作，支持超时控制、操作取消等功能 解决可能长时间运行的任务，需要停止的问题
// 参数：
//
//	ctx: 上下文对象，可携带超时时间（如context.WithTimeout）或取消信号
//	fn: 回调函数，接收*sql.DB实例，在函数内执行具体的数据库操作
//
// 返回值：
//
//	error: 操作失败返回错误（如未连接、上下文取消）；成功返回nil
func (m *MySqlDatabase) WithContext(ctx context.Context, fn func(*sql.DB) error) error {
	// context 是 Go 语言标准库提供的一个工具，核心作用是 “传递操作的生命周期控制信号”
	// fn 函数内部可以使用 *sql.DB 的 带上下文的方法（比如 db.QueryContext(ctx, ...)、db.ExecContext(ctx, ...)），这些方法会 “监听” ctx 的信号：
	// 超时控制：如果 ctx 是通过 context.WithTimeout 创建的（比如设置 3 秒超时），那么 QueryContext 会在 3 秒内没结果时自动中断，返回超时错误，避免查询一直卡着占用连接。
	// 主动取消：如果外部通过 context.WithCancel 创建 ctx，并在操作中途调用 cancel()，QueryContext 会立即停止，释放资源。
	// 传递请求信息：ctx 可以通过 ctx.WithValue 携带一些临时数据（比如当前请求的用户 ID），在 fn 内部可以读取这些数据（不过数据库操作中较少用这个功能）

	// 解决可能长时间运行的任务，防止阻塞

	if m.db == nil { // 检查连接状态
		return ErrDBNotConnected
	}
	// 调用回调函数，将db实例传入，在函数内可使用ctx执行带上下文的操作（如db.QueryContext）
	return fn(m.db)
}
